package ru.evreke

import BigShip
import Board
import Coordinates
import FieldConstants
import HugeShip
import MediumShip
import Ship
import SmallShip
import kotlin.test.*

class BoardTests {
    @Test
    fun placingSmallVerticalShipOnEdgesSucceeded() {
        val board = Board.initBoard()
        val verticalShip = SmallShip(Direction.VERTICAL)

        assertShipPlaced(board, Coordinates(0, 0), verticalShip)
        assertShipPlaced(board, Coordinates(9, 0), verticalShip)
        assertShipPlaced(board, Coordinates(0, 9), verticalShip)
        assertShipPlaced(board, Coordinates(9, 9), verticalShip)
    }

    @Test
    fun placingSmallHorizontalShipOnEdgesSucceeded() {
        val board = Board.initBoard()
        val horizontalShip = SmallShip(Direction.HORIZONTAL)

        assertShipPlaced(board, Coordinates(0, 0), horizontalShip)
        assertShipPlaced(board, Coordinates(9, 0), horizontalShip)
        assertShipPlaced(board, Coordinates(0, 9), horizontalShip)
        assertShipPlaced(board, Coordinates(9, 9), horizontalShip)
    }

    @Test
    fun placingMediumVerticalShipOnEdgesSucceeded() {
        val board = Board.initBoard()
        val verticalShip = MediumShip(Direction.VERTICAL)

        assertShipPlaced(board, Coordinates(0, 0), verticalShip)
        assertShipPlaced(board, Coordinates(9, 0), verticalShip)
        assertShipPlaced(board, Coordinates(0, 8), verticalShip)
        assertShipPlaced(board, Coordinates(9, 8), verticalShip)
    }

    @Test
    fun placingMediumHorizontalShipOnEdgesSucceeded() {
        val board = Board.initBoard()
        val horizontalShip = MediumShip(Direction.HORIZONTAL)

        assertShipPlaced(board, Coordinates(0, 0), horizontalShip)
        assertShipPlaced(board, Coordinates(8, 0), horizontalShip)
        assertShipPlaced(board, Coordinates(0, 9), horizontalShip)
        assertShipPlaced(board, Coordinates(8, 9), horizontalShip)
    }

    @Test
    fun placingBigVerticalShipOnEdgesSucceeded() {
        val board = Board.initBoard()
        val verticalShip = BigShip(Direction.VERTICAL)

        assertShipPlaced(board, Coordinates(0, 0), verticalShip)
        assertShipPlaced(board, Coordinates(9, 0), verticalShip)
        assertShipPlaced(board, Coordinates(0, 7), verticalShip)
        assertShipPlaced(board, Coordinates(9, 7), verticalShip)
    }

    @Test
    fun placingBigHorizontalShipOnEdgesSucceeded() {
        val board = Board.initBoard()
        val horizontalShip = BigShip(Direction.HORIZONTAL)

        assertShipPlaced(board, Coordinates(0, 0), horizontalShip)
        assertShipPlaced(board, Coordinates(7, 0), horizontalShip)
        assertShipPlaced(board, Coordinates(0, 9), horizontalShip)
        assertShipPlaced(board, Coordinates(7, 9), horizontalShip)
    }

    @Test
    fun placingHugeVerticalShipOnEdgesSucceeded() {
        val board = Board.initBoard()
        val verticalShip = HugeShip(Direction.VERTICAL)

        assertShipPlaced(board, Coordinates(0, 0), verticalShip)
        assertShipPlaced(board, Coordinates(9, 0), verticalShip)
        assertShipPlaced(board, Coordinates(0, 6), verticalShip)
        assertShipPlaced(board, Coordinates(9, 6), verticalShip)
    }

    @Test
    fun placingHugeHorizontalShipOnEdgesSucceeded() {
        val board = Board.initBoard()
        val horizontalShip = HugeShip(Direction.HORIZONTAL)

        assertShipPlaced(board, Coordinates(0, 0), horizontalShip)
        assertShipPlaced(board, Coordinates(6, 0), horizontalShip)
        assertShipPlaced(board, Coordinates(0, 9), horizontalShip)
        assertShipPlaced(board, Coordinates(6, 9), horizontalShip)
    }

    private fun assertShipPlaced(board: Board, coordinates: Coordinates, ship: Ship) {
        expect(Unit) {
            board.placeShip(
                coordinates,
                ship
            )
        }
        assertTrue {
            board.getCell(coordinates) == ship.hashCode()
        }
    }

    @Test
    fun smallHorizontalShipPlacedProperly() {
        val board = Board.initBoard()
        val ship = SmallShip(Direction.HORIZONTAL)
        board.placeShip(Coordinates(0, 0), ship)
        assertShipPlacedOnExpectedCells(board, ship)
    }

    @Test
    fun smallVerticalShipPlacedProperly() {
        val board = Board.initBoard()
        val ship = SmallShip(Direction.VERTICAL)
        board.placeShip(Coordinates(0, 0), ship)
        assertShipPlacedOnExpectedCells(board, ship)
    }

    @Test
    fun mediumHorizontalShipPlacedProperly() {
        val board = Board.initBoard()
        val ship = MediumShip(Direction.HORIZONTAL)
        board.placeShip(Coordinates(0, 0), ship)
        assertShipPlacedOnExpectedCells(board, ship)
    }

    @Test
    fun mediumVerticalShipPlacedProperly() {
        val board = Board.initBoard()
        val ship = MediumShip(Direction.VERTICAL)
        board.placeShip(Coordinates(0, 0), ship)
        assertShipPlacedOnExpectedCells(board, ship)
    }

    @Test
    fun bigHorizontalShipPlacedProperly() {
        val board = Board.initBoard()
        val ship = BigShip(Direction.HORIZONTAL)
        board.placeShip(Coordinates(0, 0), ship)
        assertShipPlacedOnExpectedCells(board, ship)
    }

    @Test
    fun bigVerticalShipPlacedProperly() {
        val board = Board.initBoard()
        val ship = BigShip(Direction.VERTICAL)
        board.placeShip(Coordinates(0, 0), ship)
        assertShipPlacedOnExpectedCells(board, ship)
    }

    @Test
    fun hugeHorizontalShipPlacedProperly() {
        val board = Board.initBoard()
        val ship = HugeShip(Direction.HORIZONTAL)
        board.placeShip(Coordinates(0, 0), ship)
        assertShipPlacedOnExpectedCells(board, ship)
    }

    @Test
    fun hugeVerticalShipPlacedProperly() {
        val board = Board.initBoard()
        val ship = HugeShip(Direction.VERTICAL)
        board.placeShip(Coordinates(0, 0), ship)
        assertShipPlacedOnExpectedCells(board, ship)
    }

    private fun assertShipPlacedOnExpectedCells(board: Board, ship: Ship) {
        repeat(ship.size) {
            when (ship.direction) {
                Direction.HORIZONTAL -> assertTrue { board.getCell(Coordinates(it, 0)) == ship.hashCode() }
                Direction.VERTICAL -> assertTrue { board.getCell(Coordinates(0, it)) == ship.hashCode() }
            }
        }
    }

    @Test
    fun placingShipWithInitialCoordinatesOutOfBoundsFails() {
        assertFails {
            Board.initBoard().placeShip(
                Coordinates(-1, 0),
                SmallShip(Direction.HORIZONTAL)
            )
        }
        assertFails {
            Board.initBoard().placeShip(
                Coordinates(10, 0),
                SmallShip(Direction.HORIZONTAL)
            )
        }
        assertFails {
            Board.initBoard().placeShip(
                Coordinates(0, -1),
                SmallShip(Direction.VERTICAL)
            )
        }
        assertFails {
            Board.initBoard().placeShip(
                Coordinates(0, 10),
                SmallShip(Direction.VERTICAL)
            )
        }
        assertFails {
            Board.initBoard().placeShip(
                Coordinates(10, 10),
                SmallShip(Direction.VERTICAL)
            )
        }
        assertFails {
            Board.initBoard().placeShip(
                Coordinates(-1, -1),
                SmallShip(Direction.VERTICAL)
            )
        }
    }

    @Test
    fun placingShipWithSizeThatExceedsTheBoundariesFails() {
        assertFails {
            Board.initBoard().placeShip(
                Coordinates(10, 0),
                SmallShip(Direction.HORIZONTAL)
            )
        }
        assertFails {
            Board.initBoard().placeShip(
                Coordinates(0, 10),
                SmallShip(Direction.VERTICAL)
            )
        }
    }

    @Test
    fun placingShipInCellThatOccupiedByAnotherShipFails() {
        val coordinates = Coordinates(0, 0)
        val hugeShip = SmallShip(Direction.HORIZONTAL)
        val board = Board.initBoard().also {
            it.placeShip(
                coordinates,
                hugeShip
            )
        }

        assertFails {
            val smallShip = SmallShip(Direction.HORIZONTAL)
            board.placeShip(coordinates, smallShip)
        }
    }

    @Test
    fun placingDifferentVerticalShipsInAdjacentCellsFails() {
        val hugeShipVertical = HugeShip(Direction.VERTICAL)
        testAdjacentCellFailure(Board.initBoard(), hugeShipVertical, SmallShip(Direction.VERTICAL))
    }

    @Test
    fun placingDifferentHorizontalShipsInAdjacentCellsFails() {
        val hugeShipHorizontal = HugeShip(Direction.HORIZONTAL)
        testAdjacentCellFailure(Board.initBoard(), hugeShipHorizontal, SmallShip(Direction.HORIZONTAL))
    }

    @Test
    fun placingHorizontalShipInAdjacentCellWithAnotherVerticalShipFails() {
        val hugeShipHorizontal = HugeShip(Direction.HORIZONTAL)
        testAdjacentCellFailure(Board.initBoard(), hugeShipHorizontal, SmallShip(Direction.VERTICAL))
    }

    private fun testAdjacentCellFailure(board: Board, initialShip: Ship, secondShip: Ship) {
        // Nearest to center coordinates
        val dx = 5
        val dy = 5
        board.placeShip(
            Coordinates(dx, dy),
            initialShip
        )

        repeat(initialShip.size) {
            assertFails {
                if (initialShip.direction == Direction.HORIZONTAL) {
                    board.placeShip(Coordinates(it + dx, dy), secondShip)
                } else {
                    board.placeShip(Coordinates(dx, it + dy), secondShip)
                }
            }
        }
    }

    @Test
    fun testHasNoAdjacentShipSucceeded() {
        val board = Board.initBoard()
        val ship = SmallShip(Direction.VERTICAL)
        val (dx, dy) = 5 to 5
        board.placeShip(Coordinates(dx, dy), ship)

        assertEquals(FieldConstants.EMPTY, board.getCell(Coordinates(dx - 1, dy - 1)))
        assertEquals(FieldConstants.EMPTY, board.getCell(Coordinates(dx, dy - 1)))
        assertEquals(FieldConstants.EMPTY, board.getCell(Coordinates(dx + 1, dy - 1)))

        assertEquals(FieldConstants.EMPTY, board.getCell(Coordinates(dx - 1, dy)))
        assertEquals(ship.hashCode(), board.getCell(Coordinates(dx, dy)))
        assertEquals(FieldConstants.EMPTY, board.getCell(Coordinates(dx + 1, dy)))

        assertEquals(FieldConstants.EMPTY, board.getCell(Coordinates(dx - 1, dy + 1)))
        assertEquals(FieldConstants.EMPTY, board.getCell(Coordinates(dx, dy + 1)))
        assertEquals(FieldConstants.EMPTY, board.getCell(Coordinates(dx + 1, dy + 1)))
    }

    /**
     * Tests that ships cannot be positioned around another within one cell distance
     */
    @Test
    fun testHasAdjacentShipSucceeded() {
        (-1..1).forEach { x ->
            (-1..1).forEach { y ->
                val board = Board.initBoard()
                val ship = SmallShip(Direction.VERTICAL)
                val adjacentShip = SmallShip(Direction.VERTICAL)
                val (dx, dy) = 5 to 5
                board.placeShip(Coordinates(dx, dy), ship)

                if (dx - x != dx || dy - y != dy) {
                    assertFails {
                        board.placeShip(Coordinates(dx - x, dy - y), adjacentShip)
                    }
                }
            }
        }
    }
}